package top.malaoshi.webmvc;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
import top.malaoshi.sprout.ioc.DefaultSingletonBeanRegistry;
import top.malaoshi.util.Tuple2;
import top.malaoshi.webmvc.annotation.handler.RequestMappingResolver;
import top.malaoshi.webmvc.exception.RequestMappingException;
import top.malaoshi.webmvc.exception.SameURLException;
import top.malaoshi.webmvc.exception.URLException;
import top.malaoshi.webmvc.info.MethodInfo;
import top.malaoshi.webmvc.info.RequestMappingInfo;
import top.malaoshi.webmvc.utils.MethodUtil;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 处理Url与方法映射
 */
public class WebApplicationStarter {

    DefaultSingletonBeanRegistry registry = DefaultSingletonBeanRegistry.me();

    ResponseHandler jsonResponseHandler = new JsonResponseHandler();
    ResponseHandler textResponseHandler = new TextResponseHandler();

    /**
     * url路径和方法的映射
     */
    private Map<String, MethodInfo> urlMapping = new HashMap<>();

    public void start() throws Exception {

        // 3 url映射
        try {
            doUrlMapping();
        }catch (Exception e){
            throw e;
        }

    }

    /**
     * 处理方法和路径的映射
     */
    private void doUrlMapping() throws Exception {
//        RequestMappingHandler requestMappingHandler = new RequestMappingHandler();
        RequestMappingResolver requestMappingResolver = new RequestMappingResolver();
        //将controller中对应方法和配置的url路径映射起来
        registry.forEachSingletonObjects((beanName,instance)->{
            Class<?> clazz = instance.getClass();
            if (clazz.isAnnotationPresent(Controller.class)) {
                // 判断类上是否有 ResponseBody
                boolean hasResponseBodyOfClass = clazz.isAnnotationPresent(ResponseBody.class);

                // 处理类上的 RequestMapping 注解
                String[] classURLs = null;
                if (clazz.isAnnotationPresent(RequestMapping.class)) {
                    RequestMapping anno = clazz.getAnnotation(RequestMapping.class);
//                    requestMappingHandler.setRequestMapping(anno);
//                    classURLs = requestMappingHandler.getValueAndPath();
                    RequestMappingInfo requestMappingInfo = requestMappingResolver.resolver(anno);
                    classURLs = requestMappingInfo.getValuesAndPaths();
                }
                // 处理方法上的 RequestMapping 注解
                Method[] methods = clazz.getDeclaredMethods();
                for (Method method : methods) {
                    if(!MethodUtil.hasRequestMapping(method)){
                        continue;
                    }
                    // 判断方法上是否有 ResponseBody
                    boolean hasResponseBodyOfMethods = method.isAnnotationPresent(ResponseBody.class);

                    Tuple2<String[], RequestMappingInfo> resolverRequestResult = resolverRequest(method, requestMappingResolver);
                    String[] methodURLs = resolverRequestResult.getFirst();
                    RequestMappingInfo requestMappingInfo = resolverRequestResult.getSecond();

                    String[] urls = joinURL(classURLs, methodURLs,method);

                    // 处理返回结果
                    Class<?> returnType = method.getReturnType();
                    ResponseHandler responseHandler = null;
                    // 返回 Json
                    if(hasResponseBodyOfClass || hasResponseBodyOfMethods){
                        responseHandler = jsonResponseHandler;
                    }else if(returnType == null){

                    }else if(returnType == String.class){
                        responseHandler = textResponseHandler;
                    }else if(returnType == ModelAndView.class){
                        List thymeleafResponseHandlerList = registry.getSingletonsByType(ThymeleafResponseHandler.class);
                        if( thymeleafResponseHandlerList!=null && !thymeleafResponseHandlerList.isEmpty()){
                            responseHandler = (ResponseHandler)thymeleafResponseHandlerList.get(0);
                        }
                    }
                    MethodInfo mi = MethodInfo.of(beanName, method, responseHandler,requestMappingInfo);
                    // 解析该方法的形参
                    MethodArgumentResolver.handler(method,mi);

                    putURL(urls,method,mi);

                }
            }else if (clazz.isAnnotationPresent(RestController.class)) {
                String[] classURLs = null;
                // 处理类上的 RequestMapping 注解
                if (clazz.isAnnotationPresent(RequestMapping.class)) {
                    RequestMapping anno = clazz.getAnnotation(RequestMapping.class);
//                    requestMappingHandler.setRequestMapping(anno);
//                    classURLs = requestMappingHandler.getValueAndPath();
                    RequestMappingInfo requestMappingInfo = requestMappingResolver.resolver(anno);
                    classURLs = requestMappingInfo.getValuesAndPaths();
                }
                // 处理方法上的 RequestMapping 注解
                Method[] methods = clazz.getDeclaredMethods();
                for (Method method : methods) {
                    if(!MethodUtil.hasRequestMapping(method)){
                        continue;
                    }
                    Tuple2<String[], RequestMappingInfo> resolverRequestResult = resolverRequest(method, requestMappingResolver);
                    String[] methodURLs = resolverRequestResult.getFirst();
                    RequestMappingInfo requestMappingInfo = resolverRequestResult.getSecond();

                    String[] urls = joinURL(classURLs, methodURLs,method);


                    MethodInfo mi = MethodInfo.of(beanName, method, jsonResponseHandler,requestMappingInfo);
                    // 解析该方法的形参
                    MethodArgumentResolver.handler(method,mi);

                    putURL(urls,method,mi);


                }
            }

        });

    }

    /**
     * 将url和MethodInfo放入到 urlHandlers 中
     * @param urls
     * @param method 用于抛异常
     * @param mi
     */
    private void putURL(String[] urls,Method method,MethodInfo mi){
        for(String url : urls) {
            if(urlMapping.containsKey(url)){
                MethodInfo methodInfo = urlMapping.get(url);
                throw new SameURLException(url,method,methodInfo.getMethod());
            }
            urlMapping.put(url, mi);

        }
    }
    /**
     * 解析方法上的 RequestMapping、GetMapping、PostMapping注解
     * 方法上的 RequestMapping、GetMapping、PostMapping 等注解，只能有一个，否则抛异常
     * @return
     */
    private Tuple2<String[], RequestMappingInfo> resolverRequest(Method method, RequestMappingResolver requestMappingResolver){
        String[] methodURLs = null;

        Annotation[] annotations = method.getAnnotations();
        RequestMappingInfo requestMappingInfo = null;
        // RequestMapping、GetMapping、PostMapping 一类注解的数量，如果大于1就报错
        int requestMappingNum = 0;
        for(Annotation item : annotations){
            Class<? extends Annotation> annoClass = item.annotationType();
            if(annoClass == RequestMapping.class){
                RequestMapping mapping = (RequestMapping)item;
                requestMappingInfo = requestMappingResolver.resolver(mapping);
                methodURLs = requestMappingInfo.getValuesAndPaths();
                requestMappingNum++;
            }else if(annoClass == GetMapping.class){
                GetMapping mapping = (GetMapping)item;
                requestMappingInfo = requestMappingResolver.resolver(mapping);
                methodURLs = requestMappingInfo.getValuesAndPaths();

                requestMappingNum++;
            }else if(annoClass == PostMapping.class){
                PostMapping mapping = (PostMapping)item;
                requestMappingInfo = requestMappingResolver.resolver(mapping);
                methodURLs = requestMappingInfo.getValuesAndPaths();
                requestMappingNum++;

            }
        }
        if(requestMappingNum>1){
            String className = method.getDeclaringClass().getName();
            String methodName = method.getName();
            String msg = String.format("'%s . %s'不能有多个 RequestMapping、GetMapping、PostMapping注解", className,methodName);
            throw new RequestMappingException(msg);
        }
        return Tuple2.create(methodURLs,requestMappingInfo);
    }

    /**
     * 拼装url
     * @param classURLs 类的url
     * @param methodURLs 方法的url
     * @param method 用于报错提示错误位置
     * @return
     */
    private String[] joinURL(String[] classURLs,String[] methodURLs,Method method){
        List<String> urls = new ArrayList<>();
        if(classURLs!=null && methodURLs!=null){
            for(String classURL : classURLs) {
                for (String methodURL : methodURLs) {
                    String url = classURL + methodURL;
                    checkURL(url,method);
                    urls.add(url);
                }
            }
        }else if(classURLs !=null && methodURLs == null){
            for(String classURL : classURLs) {
                checkURL(classURL,method);
                urls.add(classURL);
            }
        }else if(classURLs ==null && methodURLs != null){
            for(String methodURL : methodURLs) {
                checkURL(methodURL,method);
                urls.add(methodURL);
            }
        }
        String[] arr = new String[urls.size()];
        return urls.toArray(arr);
    }

    /**
     * 校验url
     * @param url
     */
    private void checkURL(String url,Method method){
        if(url.charAt(0) != '/'){
            String msg = String.format("URL [ %s ]必须以 / 开头，错误在 [ %s::%s() ]",
                    url,
                    method.getDeclaringClass().getName(),
                    method.getName()
            );
            throw new URLException(msg);
        }
    }

    public Map<String, MethodInfo> getUrlMapping() {
        return urlMapping;
    }
}
